import functools
import operator
import collections
import propertylib
from multimethod import multimethod

#===============================================================================
# API Identifiers
#===============================================================================
def api_decorate(argtype, *args, **kwds):
    '''Decorates function to participate to a Idiom API'''
    if len(args) == 2:
        identifier, func = args
        func.identifier = identifier
        func.argtype = argtype
        func.original = func

        # Mark function as not a method
        if not kwds.pop('method', True):
            before_method = func
            func.static_func = func.original

            @functools.wraps(func)
            def func(self, arglst):
                return before_method(arglst)

        # Support expansion of function arguments
        if kwds.pop('expand', False):
            before_expand = func
            func.expanded_func = func.original

            @functools.wraps(func)
            def func(self, arglst):
                return before_expand(self, *arglst)

        # Support evalargs
        if kwds.pop('evalargs', True):
            before_evalargs = func
            func.uneval_func = func.original

            @functools.wraps(func)
            def func(self, arglst):
                eval_f = self.eval
                return before_evalargs(self, [eval_f(x) for x in arglst])

        # Look for unsupported keywords
        if kwds:
            raise TypeError('unrecognized keyword argument, %s' % kwds.pop())
        return func

    # Decorator form of this function
    elif len(args) == 1:
        return lambda x: api_decorate(argtype, args[0], x, **kwds)

    else:
        raise TypeError('accepts 1 or 2 positional arguments')

def dictfunc(*args, **kwds):
    '''Marks method or function to belong to the API as a dict function and 
    sets the corresponding identifier.'''
    return api_decorate(dict, *args, **kwds)

def listfunc(*args, **kwds):
    '''Marks method or function to belong to the API as a dict function and 
    sets the corresponding identifier.'''
    return api_decorate(list, *args, **kwds)

def value(*args, **kwds):
    '''Marks method or function to belong to the API as a dict function and 
    sets the corresponding identifier.'''
    return api_decorate(None, *args, **kwds)

class Idiom(dict):
    def __init__(self, *args, **kwds):
        super(Idiom, self).__init__(*args, **kwds)
        # TODO: re-register?

        # Register all methods 
        for arg in dir(self):
            value = getattr(self, arg)
            if hasattr(value, 'argtype'):
                self[value.identifier] = value

    #===========================================================================
    # Eval functions
    #===========================================================================
    def eval_list(self, lst):
        if len(lst) == 0:
            return lst
        elif len(lst) == 1:
            return [self.eval(lst[0])]
        else:
            head = self.eval(lst[0])
            if getattr(head, 'argtype', None) is list:
                return head(lst[1:])
            else:
                return lst

    def eval_dict(self, dic):
        functions = set()
        arg_dic = {}
        for k, v in dic.items():
            value = self.eval(k)
            if getattr(value, 'argtype', None) is dict:
                functions.add(k)
            else:
                arg_dic[k] = v

    def eval_str(self, st):
        return self.get(st, st)

    def eval_float(self, obj):
        return obj

    def eval_int(self, obj):
        return obj

    def eval_bool(self, obj):
        return obj

    def eval_none(self, obj):
        return obj

    @multimethod(dict, object)
    def eval_other(self, obj):
        return obj

    @propertylib.cache_property
    def eval(self):
        cls = type(self)
        eval_f = multimethod(collections.Sequence)(self.eval_list)
        eval_f[(collections.Mapping,)] = self.eval_dict
        eval_f[(basestring,)] = self.eval_str
        eval_f[(float,)] = self.eval_float
        eval_f[(int,)] = self.eval_int
        eval_f[(bool,)] = self.eval_bool
        eval_f[(type(None),)] = self.eval_none
        return eval_f

    #===========================================================================
    # Magic methods
    #===========================================================================
    def __call__(self, obj, **kwds):
        return self.eval(obj)

    def __repr__(self):
        return object.__repr__(self)

    #===========================================================================
    # Sub-idioms (scoping rules)
    #===========================================================================


    #===========================================================================
    # Namespace manipulations
    #===========================================================================
    def rename(self, old, new=None):
        '''Rename key or group of keys in the namespace.'''
        if new is None:
            for k, v in old.items():
                self.rename(k, v)
        else:
            self[new] = self.pop(old)

class Chico(Idiom):
    #===========================================================================
    # Basic mathematical operations
    #===========================================================================
    @listfunc('+')
    def func_add(self, arglst): return sum(arglst)

    @listfunc('*')
    def func_mul(self, arglst): return reduce(operator.mul, arglst)

    func_sub = listfunc('-', lambda self, (x, y): x - y)
    func_div = listfunc('/', lambda self, (x, y): x / y)
    func_pow = listfunc('^', lambda self, (x, y): x ^ y)

    #===========================================================================
    # Comparison and logical operations
    #===========================================================================

    func_le = listfunc('<=', lambda self, (x, y): x <= y)
    func_lt = listfunc('<', lambda self, (x, y): x < y)
    func_ge = listfunc('>=', lambda self, (x, y): x >= y)
    func_gt = listfunc('>', lambda self, (x, y): x > y)
    func_ne = listfunc('!=', lambda self, (x, y): x != y)
    func_eq = listfunc('==', lambda self, (x, y): x == y)

    @listfunc('and', evalargs=False)
    def func_and(self, (left, right)):
        return self.eval(left) and self.eval(right)

    @listfunc('or', evalargs=False)
    def func_or(self, (left, right)):
        return self.eval(left) or self.eval(right)

    @listfunc('not', expand=True)
    def func_not(self, obj):
        return not self.eval(obj)

    #===========================================================================
    # Control structures
    #===========================================================================
    @listfunc('!', evalargs=False)
    def func_bitwise(self, arglst):
        op = arglst[1]
        arglst = [arglst[0]] + arglst[2:]
        return self.eval(op)(arglst)

    @listfunc('if', evalargs=False)
    def func_if(self, (cond, do_if, do_else)):
        eval_f = self.eval
        if eval_f(cond):
            return eval_f(do_if)
        else:
            return eval_f(do_else)

    @listfunc('loop', evalargs=False)
    def func_for(self, (body, var, loop_start, stop_cond, sucs)):
        raise NotImplemented

        eval_f = self.eval
        if eval_f(cond):
            return eval_f(do_if)
        else:
            return eval_f(do_else)

    #===========================================================================
    # Data structures
    #===========================================================================

    func_list = listfunc('list', lambda self, args: list(args))
    func_list = listfunc('inert', lambda self, args: list(args), evalargs=False)
    func_str = listfunc('str', lambda self, x: str(x), expand=True)
    func_int = listfunc('int', lambda self, x: int(x), expand=True)
    func_float = listfunc('float', lambda self, x: float(x), expand=True)
    func_bool = listfunc('bool', lambda self, x: bool(x), expand=True)

    #===========================================================================
    # Function definition
    #===========================================================================

if __name__ == '__main__':
    chico = Chico()
    print chico(['-', 1, 2])
    print chico(['!', 2, 'or', 1])
    print chico(['!', 2, 'and', 1])
    print chico(['if', ['!', 2, '>', 1], 2, 1])
    print chico(['list', 1, 2, 3])
    print chico(['inert', 1, 2, 3])
    print chico(['str', 1])
    raise SystemExit(0)

    ['def', 'fname',
     [0, 0, 0], None,
     ['x', 'y', 'z'], ['+', 'x', 'y', 'z']]


    ['defm', 'fat',
                 [[0], 1],
                 [['x'], ['!', 'x', '*', ['fat', ['-', 'x', 1]]]]
    ]

    '''
    # single line:
    ["def", "func", ["x"], ["*", "x", "x"]]
    
    [def func [x] [* x x]]
    
    # multi line
    ["def", "func", ["x"], 
        ["*", "x", "x"]]
    
    def func [x]:
        [* x x]
    end def: 
    
    
    {'foo': ['+', 1, 2, 3],
     'bar: ['def', 'double', ['x'],
            ['+', 'x', 'x']}
    
    {foo: [+ 1 2 3]
     bar: [def double [x]
               [+ x x]]}
    
    foo: [+ 1 2 3]
    bar: [def double [x]
           [+ x x]]
    
    [[def f [x] x] [def g[x] [! x * x]]]
    [["def", "f", ["x"]
          "x"],
     ["def", "g", ["x"]
          ["!", "x", "*", "x"]]
          
    
    
    
    '''
    '''
    [ apply
    [ defm fat
        [ [0 1]
          [x [! x * [fat [! x - 1]]
        ]
    ] [$ 1 2 ... 10]
    ]
    '''
    '''
    import [syntax infix] :as: .
    
    def fat n:
        [. n * fat(. x - 1)]
    
    
    [apply    
        def fat n:
            [. n * fat(. x - 1)]
        end def:
    [$ 1 2 3 5 ... 20] ]
    
    { # Comment
      key: value
      
      #Other comment
      values:
          apply:
          function
               def fat n:
                   [! n * fat(. x - 1)]
               end def:
          on list
               [$ 1 2 3 5 ... 20]
          end apply:
          
      data:
          str:
             [. [* i i i] foreach i [$ 1 ... 10]]
          end str:
    }
    
    [def quicksort [lst]
        first: [first lst]
        rest: [rest lst]
        =return=:  
            [join [! x foreach x rest [! x < first ]]
                  [first]
                  [! x foreach x rest [! x > first]]]]
                  
    ["def", "quicksort", ["lst"], 
        {"first": ["first", "lst"]
         "rest": ["rest", "lst"]
         "=return=": ["join", ["!", "x", "foreach", "x", "rest", ["!", "x", "<", "first"]],
                  ["first"],
                  ["!", "x", "foreach", "x", "rest", ["!", "x", ">", "first"]]]}]
    
                  
                  
    func(x y) -> [func x y]
    func(x, y) -> [func x y]
    [func, x, y]
    *(x, y) -> [* x y]
    
    [map func [x1, x2, x3, ...]] <==> [# func x1 x2 x3 ...]
    
    
    ["def", "quicksort", "lst",
        { "first": ["first", "lst"],
           "rest": ["rest", "lst"],
           "=return=":  
                ["join", ["!", "x", "foreach", "x", "rest", ["!", "x", "<", "first"]],
                      ["<<", "first"],
                      ["!", "x", "foreach", "x", "rest", ["!", "x", ">", "first"]]],
        }
    ]
    
    defm +: 
        [x 0]: x
        [x y]: [add [++ x] [-- y]]
    end defm  
    
    defm +: 
        [x 0]: x
        [x y]: [add [++ x] [-- y]]
    end defm
    
    {=importing=: [bar [eggs.foo + bar]]
     foo: [bar foobar]
     ham: [[bar other_bar] spam]
     spam.all: *
     eggs: eggs.*
    }
    
    { =dispatch=: null
      +: [x] 
    }
    
    
    # comment
    if cond:
        foo
    else (it doesn't look so, but this is a comment line)
        bar
    end if:
    
    if cond:
        foo
    :elif: dsfs
    :else:
        bar
    
    
    [$: x1 x2]
    [with [open fpath]:
        dfdfsd
        dfsdfs
        dsfsdf
    ]
    [: inert ] -> inert -> "inert"
    
    no_bad_chars -> "no_bad_chars"
    `some bad chars` -> "some bad chars"
    "a string" -> ["str" "a string"]
    '''

    '''
    x y:
      z k
      i j
    -> [x y z k i j]
    
    atom(x) -> [atom x]
    atom[x] -> [atom x]
    atom(atom) -> [atom atom]
    atom(x, atom, z) -> [atom x atom z]
    '''
    '''
    { func: 1
      func: var
    } 
    '''

    '''
    [ defm fat
        [0 1] [x [* x [fat [- x 1]]]]
    ]
    
    defm fat:
        [0 1] [x [* x [fat [- x 1]]]]
    end
    
    !:
      x > y
    end !
         
     [if [! x > y] then foo else bar]
     [if [! x > y] foo else bar]
     [if [! x > y] foo bar]
     if([! x > y], foo, bar)
     
     (if [! x > y] then "foo" else "bar")
     
     if: []
       cond: [! x > y]
       then: []
       else: []
    
    if: [[! x > y]] 
       else: 'bar'
      
    =result=: [m: (a + b) * a * b]
        m
      
    def func [x y]:
      null
    end def;
      
       
    YAML
    [def, fat, [x, y], 
        [x]]
        
    def 
    
    '''
